package xyz.qy.implatform.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xyz.qy.imclient.IMClient;
import xyz.qy.imclient.annotation.Lock;
import xyz.qy.imcommon.contant.IMRedisKey;
import xyz.qy.implatform.contant.Constant;
import xyz.qy.implatform.dto.RegionGroupDTO;
import xyz.qy.implatform.entity.Region;
import xyz.qy.implatform.entity.RegionGroup;
import xyz.qy.implatform.entity.RegionGroupMember;
import xyz.qy.implatform.entity.User;
import xyz.qy.implatform.exception.GlobalException;
import xyz.qy.implatform.mapper.RegionGroupMapper;
import xyz.qy.implatform.service.IRegionGroupMemberService;
import xyz.qy.implatform.service.IRegionGroupService;
import xyz.qy.implatform.service.IRegionService;
import xyz.qy.implatform.service.IUserService;
import xyz.qy.implatform.session.SessionContext;
import xyz.qy.implatform.session.UserSession;
import xyz.qy.implatform.util.BeanUtils;
import xyz.qy.implatform.util.DateTimeUtils;
import xyz.qy.implatform.util.RedisCache;
import xyz.qy.implatform.vo.RegionGroupMemberVO;
import xyz.qy.implatform.vo.RegionGroupVO;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 地区群聊serviceImpl
 *
 * @author Polaris
 * @since 2024-10-26
 */
@Slf4j
@Service
public class RegionGroupServiceImpl extends ServiceImpl<RegionGroupMapper, RegionGroup> implements IRegionGroupService {
    @Resource
    private IRegionService regionService;

    @Resource
    private IRegionGroupMemberService regionGroupMemberService;

    @Resource
    private IUserService userService;

    @Resource
    private IMClient imClient;

    @Resource
    private RedisCache redisCache;

    @Override
    public List<RegionGroupVO> findRegionGroups() {
        UserSession session = SessionContext.getSession();
        Long userId = session.getUserId();

        // 1、查询用户常驻地区群聊
        List<RegionGroupMember> regionGroupMembers = regionGroupMemberService.findNoQuitByUserId(session.getUserId());
        if (CollectionUtils.isEmpty(regionGroupMembers)) {
            return Collections.emptyList();
        }

        List<Long> regionGroupIds = regionGroupMembers.stream()
                .map(RegionGroupMember::getRegionGroupId).collect(Collectors.toList());

        LambdaQueryWrapper<RegionGroup> groupWrapper = Wrappers.lambdaQuery();
        groupWrapper.in(RegionGroup::getId, regionGroupIds);
        // 查询用户地区群聊信息
        List<RegionGroup> regionGroupList = this.list(groupWrapper);
        List<RegionGroupVO> regionGroupVOS = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(regionGroupList)) {
            regionGroupList.forEach(item -> {
                RegionGroupVO regionGroupVO = BeanUtils.copyProperties(item, RegionGroupVO.class);
                regionGroupVO.setJoinType(1);
                regionGroupVOS.add(regionGroupVO);
            });
        }

        //2、查询用户临时加入的地区群聊
        Collection<String> keys = redisCache.keys(IMRedisKey.IM_USER_TEMP_REGION_GROUP + userId + ":*");
        for (String key : keys) {
            Object object = redisCache.getCacheObject(key);
            RegionGroup regionGroup = Convert.convert(RegionGroup.class, object);
            RegionGroupVO regionGroupVO = BeanUtils.copyProperties(regionGroup, RegionGroupVO.class);
            regionGroupVO.setJoinType(0);
            regionGroupVOS.add(regionGroupVO);
        }

        return regionGroupVOS;
    }

    @Override
    public List<RegionGroupMemberVO> findRegionGroupMembers(Long regionGroupId) {
        List<RegionGroupMember> regionGroupMembers = regionGroupMemberService.findByRegionGroupId(regionGroupId);
        if (CollectionUtils.isEmpty(regionGroupMembers)) {
            return Collections.emptyList();
        }
        List<Long> userIds = regionGroupMembers.stream().map(RegionGroupMember::getUserId).collect(Collectors.toList());
        List<User> userList = userService.listByIds(userIds);
        List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
        Map<Long, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
        return regionGroupMembers.stream().map(m -> {
            RegionGroupMemberVO vo = BeanUtils.copyProperties(m, RegionGroupMemberVO.class);
            if (StringUtils.isBlank(vo.getHeadImage())) {
                User user = userMap.get(vo.getUserId());
                vo.setHeadImage(user.getHeadImage());
            }
            vo.setOnline(onlineUserIds.contains(m.getUserId()));
            return vo;
        }).sorted((m1,m2)-> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
    }

    @Lock(prefix = "im:region:group:member:modify", key = "#regionGroupDTO.getCode()")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public RegionGroupVO joinRegionGroup(RegionGroupDTO regionGroupDTO) {
        UserSession session = SessionContext.getSession();
        Region region = regionService.findRegionByCode(regionGroupDTO.getCode());
        if (ObjectUtil.isNull(region) || region.getDeleted()) {
            throw new GlobalException("所选地区不存在");
        }

        // 判断用户加入类型
        Integer joinType = regionGroupDTO.getJoinType();

        // 临时加入
        if (joinType == 0) {
            RegionGroup regionGroup = tempJoinRegionGroupHandle(regionGroupDTO, region, session);
            RegionGroupVO regionGroupVO = BeanUtils.copyProperties(regionGroup, RegionGroupVO.class);
            regionGroupVO.setJoinType(0);
            return regionGroupVO;
        } else {
            RegionGroup regionGroup = permanentJoinRegionGroupHandle(regionGroupDTO, region, session);
            RegionGroupVO regionGroupVO = BeanUtils.copyProperties(regionGroup, RegionGroupVO.class);
            regionGroupVO.setJoinType(1);
            return regionGroupVO;
        }
    }

    private RegionGroup permanentJoinRegionGroupHandle(RegionGroupDTO regionGroupDTO, Region region, UserSession session) {
        Collection<String> keys = redisCache.keys(IMRedisKey.IM_USER_TEMP_REGION_GROUP + session.getUserId() + ":*");

        // 判断选择的地区群聊是否已加入
        for (String key : keys) {
            if (key.equals(IMRedisKey.IM_USER_TEMP_REGION_GROUP + session.getUserId() + ":" + regionGroupDTO.getCode())) {
                throw new GlobalException("您已临时加入当前地区群聊，请先退出");
            }
        }

        // 查询用户所有常驻群聊
        List<RegionGroupMember> regionGroupMembers = regionGroupMemberService.findByUserId(session.getUserId());

        // 计算未退出数量
        long count = regionGroupMembers.stream().filter(item -> !item.getQuit()).count();
        if (count >= 3) {
            throw new GlobalException("每位用户最多只能常驻3个地区群聊");
        }

        // 若用户历史加入的地区群聊超过3个，判断距离最近一次加入的时间是否超过30分钟
        if (regionGroupMembers.size() >= 3) {
            checkLastPermanentJoinInterval(regionGroupMembers);
        }
        return getPermanentJoinRegionGroupNum(regionGroupDTO, region, session);
    }

    private void checkLastPermanentJoinInterval(List<RegionGroupMember> regionGroupMembers) {
        Collections.sort(regionGroupMembers, Comparator.comparing(RegionGroupMember::getCreateTime, Comparator.reverseOrder()));

        Date createTime = regionGroupMembers.get(0).getCreateTime();

        Date now = new Date();
        long between = DateUtil.between(createTime, now, DateUnit.MINUTE);
        if (between < 30) {
            throw new GlobalException("距离您上次加入地区群聊未超过30分钟，请稍后再尝试");
        }
    }

    private RegionGroup tempJoinRegionGroupHandle(RegionGroupDTO regionGroupDTO, Region region, UserSession session) {
        Long userId = session.getUserId();
        // 判断当前用户加入的临时地区群聊redis数量
        Collection<String> keys = redisCache.keys(IMRedisKey.IM_USER_TEMP_REGION_GROUP + userId + ":*");
        if (keys.size() >= 3) {
            throw new GlobalException("每位用户最多只能临时加入3个地区群聊");
        }

        // 判断选择的地区群聊是否已加入
        for (String key : keys) {
            if (key.equals(IMRedisKey.IM_USER_TEMP_REGION_GROUP + userId + ":" + regionGroupDTO.getCode())) {
                throw new GlobalException("您已加入当前地区群聊");
            }
        }

        Boolean exists = redisCache.hasKey(IMRedisKey.IM_REGION_GROUP_USER_TEMP_JOIN + userId);

        // 判断用户6小时内临时加入的地区群聊是否达到上限
        int count = redisCache.incrementInt(IMRedisKey.IM_REGION_GROUP_USER_TEMP_JOIN + userId);
        if (!exists) {
            redisCache.expire(IMRedisKey.IM_REGION_GROUP_USER_TEMP_JOIN + userId, 6, TimeUnit.HOURS);
        }
        if (count > 6) {
            long expire = redisCache.getExpire(IMRedisKey.IM_REGION_GROUP_USER_TEMP_JOIN + userId);
            throw new GlobalException("您在6小时内临时加入的地区群聊数量已达上限，请在" + DateTimeUtils.getTimeValueDesc(expire) + "后再尝试");
        }

        // 判断要加入的地区群聊编号
        RegionGroup regionGroup = getTempJoinRegionGroupNum(region, userId);

        this.saveOrUpdate(regionGroup);

        // 保存用户临时加入的地区群聊到redis
        redisCache.setCacheObject(IMRedisKey.IM_USER_TEMP_REGION_GROUP + userId + ":" + regionGroupDTO.getCode(),
                regionGroup, 2, TimeUnit.HOURS);

        // 保存地区群聊对应编号的临时用户数据到redis
        redisCache.setCacheObject(IMRedisKey.IM_REGION_GROUP_NUM_TEMP_USER
                + regionGroup.getCode() + ":" + regionGroup.getNum() + ":" + userId, session, 2, TimeUnit.HOURS);
        return regionGroup;
    }

    private RegionGroup getPermanentJoinRegionGroupNum(RegionGroupDTO regionGroupDTO, Region region, UserSession session) {
        LambdaQueryWrapper<RegionGroup> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(RegionGroup::getCode, region.getCode());
        queryWrapper.eq(RegionGroup::getDeleted, false);

        List<RegionGroup> regionGroupList = this.list(queryWrapper);

        // 为空代表当前地区没有创建群聊
        if (CollectionUtils.isEmpty(regionGroupList)) {
            RegionGroup regionGroup = new RegionGroup();
            regionGroup.setCode(region.getCode());
            regionGroup.setNum(Constant.ROME_NUM[0]);
            regionGroup.setRegionGroupName(generateRegionGroupName(region) + "-" + regionGroup.getNum());
            regionGroup.setRemark(region.getName() + "-" + regionGroup.getNum());
            regionGroup.setCreateBy(session.getUserId());
            this.saveOrUpdate(regionGroup);

            saveRegionGroupMember(new RegionGroupMember(), regionGroupDTO, regionGroup, session);
            return regionGroup;
        }

        // 已存在的地区群聊编号
        List<String> existsNum = new ArrayList<>();

        // 不为空判断要加入的地区群聊常驻用户不超过200的群聊，获取第一个
        for (RegionGroup regionGroup : regionGroupList) {
            existsNum.add(regionGroup.getNum());
            // 查询当前区域下所有群聊常驻人员
            List<RegionGroupMember> regionGroupMembers = regionGroupMemberService
                    .findByRegionGroupId(regionGroup.getId());

            // 计算所有未退出常驻人员数量
            long count = regionGroupMembers.stream().filter(item -> !item.getQuit()).count();
            if (count >= 20) {
                continue;
            }
            Optional<RegionGroupMember> optional = regionGroupMembers.stream()
                    .filter(item -> session.getUserId().equals(item.getUserId())).findFirst();
            RegionGroupMember regionGroupMember = optional.orElseGet(RegionGroupMember::new);
            this.saveOrUpdate(regionGroup);
            saveRegionGroupMember(regionGroupMember, regionGroupDTO, regionGroup, session);
            return regionGroup;
        }

        // 执行到此处说明所有已存在的地区群聊编号常驻用户已满，判断当前地区是否还可以创建地区群聊
        for (String num : Constant.ROME_NUM) {
            if (existsNum.contains(num)) {
                continue;
            }
            RegionGroup regionGroup = new RegionGroup();
            regionGroup.setNum(num);
            regionGroup.setCode(region.getCode());
            regionGroup.setRegionGroupName(generateRegionGroupName(region) + "-" + regionGroup.getNum());
            regionGroup.setRemark(region.getName() + "-" + regionGroup.getNum());
            regionGroup.setCreateBy(session.getUserId());
            this.saveOrUpdate(regionGroup);

            saveRegionGroupMember(new RegionGroupMember(), regionGroupDTO, regionGroup, session);
            return regionGroup;
        }

        throw new GlobalException("当前地区常驻用户已满，不能再加入");
    }

    private void saveRegionGroupMember(RegionGroupMember regionGroupMember, RegionGroupDTO regionGroupDTO,
                                       RegionGroup regionGroup, UserSession session) {
        regionGroupMember.setQuit(false);
        regionGroupMember.setUserId(session.getUserId());
        regionGroupMember.setRegionGroupId(regionGroup.getId());
        regionGroupMember.setChatName(StringUtils.isNotBlank(regionGroupDTO.getNickname()) ?
                regionGroupDTO.getNickname() : session.getNickName());
        regionGroupMember.setCreateBy(session.getUserId());
        regionGroupMemberService.saveOrUpdate(regionGroupMember);

        // 删除redis保存的用户临时加入的地区群聊到
        redisCache.deleteObject(IMRedisKey.IM_USER_TEMP_REGION_GROUP + session.getUserId() + ":" + regionGroupDTO.getCode());

        // 删除redis保存的地区群聊对应编号的临时用户数据
        redisCache.deleteObject(IMRedisKey.IM_REGION_GROUP_NUM_TEMP_USER
                + regionGroup.getCode() + ":" + regionGroup.getNum() + ":" + session.getUserId());
    }

    private RegionGroup getTempJoinRegionGroupNum(Region region, Long userId) {
        LambdaQueryWrapper<RegionGroup> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(RegionGroup::getCode, region.getCode());
        queryWrapper.eq(RegionGroup::getDeleted, false);

        List<RegionGroup> regionGroupList = this.list(queryWrapper);

        // 为空代表当前地区没有创建群聊
        if (CollectionUtils.isEmpty(regionGroupList)) {
            RegionGroup regionGroup = new RegionGroup();
            regionGroup.setCode(region.getCode());
            regionGroup.setNum(Constant.ROME_NUM[0]);
            regionGroup.setRegionGroupName(generateRegionGroupName(region) + "-" + regionGroup.getNum());
            regionGroup.setRemark(region.getName() + "-" + regionGroup.getNum());
            regionGroup.setCreateBy(userId);
            return regionGroup;
        }

        // 已存在的地区群聊编号
        List<String> existsNum = new ArrayList<>();

        // 不为空判断要加入的地区群聊临时用户不超过20的群聊，获取第一个
        for (RegionGroup regionGroup : regionGroupList) {
            // 判断用户是否在常驻名单里
            RegionGroupMember regionGroupMember = regionGroupMemberService
                    .findByRegionGroupIdAndUserId(regionGroup.getId(), userId);
            if (ObjectUtil.isNotNull(regionGroupMember) && !regionGroupMember.getQuit()) {
                throw new GlobalException("您已存在当前地区的常驻用户中");
            }

            existsNum.add(regionGroup.getNum());
            Collection<String> keys = redisCache.keys(IMRedisKey.IM_REGION_GROUP_NUM_TEMP_USER
                    + regionGroup.getCode() + ":" + regionGroup.getNum() + ":*");
            if (keys.size() >= 2) {
                continue;
            }
            return regionGroup;
        }

        // 执行到此处说明所有已存在的地区群聊编号临时用户已满，判断当前地区是否还可以创建地区群聊
        for (String num : Constant.ROME_NUM) {
            if (existsNum.contains(num)) {
                continue;
            }
            RegionGroup regionGroup = new RegionGroup();
            regionGroup.setNum(num);
            regionGroup.setCode(region.getCode());
            regionGroup.setRegionGroupName(generateRegionGroupName(region) + "-" + regionGroup.getNum());
            regionGroup.setRemark(region.getName() + "-" + regionGroup.getNum());
            regionGroup.setCreateBy(userId);
            return regionGroup;
        }

        throw new GlobalException("当前地区临时用户已满，不能再加入");
    }

    private String generateRegionGroupName(Region region) {
        List<Region> parentRegions = regionService.findAllParentRegionByParentCode(region.getParentCode());
        if (CollectionUtils.isEmpty(parentRegions)) {
            return region.getName();
        }
        parentRegions.sort((Comparator.comparingInt(Region::getLevel)));
        parentRegions.add(region);

        return parentRegions.stream().map(Region::getName).collect(Collectors.joining("->"));
    }

    @Override
    public void quitRegionGroup(RegionGroupDTO regionGroupDTO) {
        if (ObjectUtil.isNull(regionGroupDTO.getId())
            || !Arrays.asList(Constant.ROME_NUM).contains(regionGroupDTO.getNum())) {
            throw new GlobalException("参数异常");
        }
        UserSession session = SessionContext.getSession();
        if (regionGroupDTO.getJoinType() == 0) {
            quitTempRegionGroup(regionGroupDTO, session.getUserId());
        } else if (regionGroupDTO.getJoinType() == 1) {
            quitPermanentRegionGroup(regionGroupDTO, session.getUserId());
        }
    }

    private void quitTempRegionGroup(RegionGroupDTO regionGroupDTO, Long userId) {
        // 直接根据key删除redis数据

        // 删除redis保存的用户临时加入的地区群聊到
        redisCache.deleteObject(IMRedisKey.IM_USER_TEMP_REGION_GROUP + userId + ":" + regionGroupDTO.getCode());

        // 删除redis保存的地区群聊对应编号的临时用户数据
        redisCache.deleteObject(IMRedisKey.IM_REGION_GROUP_NUM_TEMP_USER
                + regionGroupDTO.getCode() + ":" + regionGroupDTO.getNum() + ":" + userId);
    }

    private void quitPermanentRegionGroup(RegionGroupDTO regionGroupDTO, Long userId) {
        // 查询用户常驻地区群聊信息
        RegionGroupMember regionGroupMember = regionGroupMemberService.findByRegionGroupIdAndUserId(regionGroupDTO.getId(), userId);
        if (ObjectUtil.isNull(regionGroupMember)) {
            return;
        }
        regionGroupMember.setQuit(true);
        regionGroupMemberService.updateById(regionGroupMember);
    }
}
